package edu.whu.tmdb.query.operations.impl;

import edu.whu.tmdb.query.operations.Exception.ErrorList;
import edu.whu.tmdb.query.operations.Insert;
import edu.whu.tmdb.storage.memory.MemManager;
import edu.whu.tmdb.storage.memory.SystemTable.*;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectItem;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.HashSet;
import java.util.List;

import edu.whu.tmdb.storage.memory.Tuple;
import edu.whu.tmdb.query.operations.CreateDeputyClass;
import edu.whu.tmdb.query.operations.Exception.TMDBException;
import edu.whu.tmdb.query.operations.utils.MemConnect;
import edu.whu.tmdb.query.operations.utils.SelectResult;


public class CreateDeputyClassImpl implements CreateDeputyClass {
    private final MemConnect memConnect;

    public CreateDeputyClassImpl() {
        this.memConnect = MemConnect.getInstance(MemManager.getInstance());
    }

    @Override
    public boolean createDeputyClass(Statement stmt) throws TMDBException, IOException {
        return execute((net.sf.jsqlparser.statement.create.deputyclass.CreateDeputyClass) stmt);
    }

    public boolean execute(net.sf.jsqlparser.statement.create.deputyclass.CreateDeputyClass stmt) throws TMDBException, IOException {
        // 1.获取代理类名、代理类型、select元组
        String deputyClassName = stmt.getDeputyClass().toString();  // 代理类名
        if (memConnect.classExist(deputyClassName)) {
            throw new TMDBException(ErrorList.TABLE_ALREADY_EXISTS, deputyClassName);
        }
        int deputyType = getDeputyType(stmt);   // 代理类型
        Select selectStmt = stmt.getSelect();
        SelectResult selectResult = getSelectResult(selectStmt);

        // 2.执行代理类创建
        return createDeputyClassStreamLine(selectResult, deputyType, deputyClassName, selectStmt);
    }

    public boolean createDeputyClassStreamLine(SelectResult selectResult, int deputyType, String deputyClassName, Select selectStmt) throws TMDBException, IOException {
        int deputyId = createDeputyClass(deputyClassName, selectResult, deputyType);
        createDeputyTableItem(selectResult.getClassName(), deputyType, deputyId, selectStmt);
        createBiPointerTableItem(selectResult, deputyId);
        return true;
    }

    /**
     * 创建代理类的实现，包含代理类classTableItem的创建和switchingTableItem的创建
     *
     * @param deputyClassName 代理类名称
     * @param selectResult    代理类包含的元组列表
     * @param deputyRule      代理规则
     * @return 新建代理类ID
     */
        private int createDeputyClass(String deputyClassName, SelectResult selectResult, int deputyRule) throws TMDBException {
        // TODO-task3
        // 遍历selectResult
        int num_attr = selectResult.getAttrname().length;
        MemConnect.getClassTable().maxid++;
        int de_classid = MemConnect.getClassTable().maxid;
        for (int i = 0; i < num_attr; i++) {
            String ori_class_name = selectResult.getClassName()[i];
            String attr_name = selectResult.getAttrname()[i];
            String attr_type = selectResult.getType()[i];
            String alias     = selectResult.getAlias()[i];
            // 1.新建classTableItem
            // 使用MemConnect.getClassTableList().add()
            MemConnect.getClassTableList().add(new ClassTableItem(
                    deputyClassName,
                    de_classid,
                    num_attr,
                    i,
                    alias,
                    attr_type,
                    "de",
                    ""));
            // 2.新建switchingTableItem
            // 使用MemConnect.getSwitchingTableList().add()
            int ori_id = memConnect.getClassId(ori_class_name);
            MemConnect.getSwitchingTableList().add(new SwitchingTableItem(
                    ori_id,
                    memConnect.getAttrid(ori_id, attr_name),
                    attr_name,
                    de_classid,
                    i,
                    alias,
                    "" + deputyRule));
        }
        return de_classid;
    }

    /**
     * 新建deputyTableItem
     *
     * @param classNames 源类类名列表
     * @param deputyType 代理规则
     * @param deputyId   代理类id
     */
    public void createDeputyTableItem(String[] classNames, int deputyType, int deputyId, Select selectStmt) throws TMDBException {
        // TODO-task3
        // 使用MemConnect.getDeputyTableList().add()
        HashSet<String> set = new HashSet<String>(Arrays.stream(classNames).collect(Collectors.toSet()));
        for (String className : set) {
            MemConnect.getDeputyTableList().add(new DeputyTableItem(
                    memConnect.getClassId(className),
                    deputyId,
                    new String[]{deputyType+""},
                    selectStmt.toString()
            ));
        }
    }

    /**
     * 插入元组，并新建BiPointerTableItem
     *
     * @param selectResult 插入的元组列表
     * @param deputyId     新建代理类id
     */
    private void createBiPointerTableItem(SelectResult selectResult, int deputyId) throws TMDBException, IOException {
        // TODO-task3
        // 使用insert.execute()插入对象
        // 可调用getOriginClass(selectResult);
        // 插入会改变传入的tuple，主要是tuple id ,所以要复制保存原有的tuple id
        HashSet<Integer> res = getOriginClass(selectResult);
        List < List<Integer> > tuple_ids = new ArrayList<List<Integer>>();
        for (Tuple tuple: selectResult.getTpl().tuplelist){
            int[] ids = tuple.tupleIds;
            List<Integer> temp = new ArrayList<Integer>(ids.length);
            for (int i=0;i<ids.length;i++){
                temp.add(ids[i]);
            }
            tuple_ids.add(temp);
        }
        InsertImpl insert = new InsertImpl();
        insert.execute(deputyId,
                Arrays.asList(selectResult.getAttrname()),
                selectResult.getTpl());

        // 使用MemConnect.getBiPointerTableList().add()插入BiPointerTable

        ArrayList<Integer> deputy_id_list = insert.tupleIdList;
        for (int tuple_index = 0; tuple_index < selectResult.getTpl().tuplenum; tuple_index++) {
            //Tuple tuple = selectResult.getTpl().tuplelist.get(tuple_index);
            for (Integer class_index : res) {
                String className = selectResult.getClassName()[class_index];
                MemConnect.getBiPointerTableList().add(new BiPointerTableItem(
                        memConnect.getClassId(className),
                        tuple_ids.get(tuple_index).get(class_index),
                        deputyId,
                        deputy_id_list.get(tuple_index)
                ));
            }
        }
    }

    /**
     * 给定创建代理类语句，返回代理规则
     *
     * @param stmt 创建代理类语句
     * @return 代理规则
     */
    private int getDeputyType(net.sf.jsqlparser.statement.create.deputyclass.CreateDeputyClass stmt) {
        switch (stmt.getType().toLowerCase(Locale.ROOT)) {
            case "selectdeputy":
                return 0;
            case "joindeputy":
                return 1;
            case "uniondeputy":
                return 2;
            case "groupbydeputy":
                return 3;
        }
        return -1;
    }

    /**
     * 给定查询语句，返回select查询执行结果（创建deputyclass后面的select语句中的selectResult）
     *
     * @param selectStmt select查询语句
     * @return 查询执行结果（包含所有满足条件元组）
     */
    private SelectResult getSelectResult(Select selectStmt) throws TMDBException, IOException {
        SelectImpl selectExecutor = new SelectImpl();
        return selectExecutor.select(selectStmt);
    }

    private HashSet<Integer> getOriginClass(SelectResult selectResult) {
        ArrayList<String> collect = Arrays.stream(selectResult.getClassName()).collect(Collectors.toCollection(ArrayList::new));
        HashSet<String> collect1 = Arrays.stream(selectResult.getClassName()).collect(Collectors.toCollection(HashSet::new));
        HashSet<Integer> res = new HashSet<>();
        for (String s : collect1) {
            res.add(collect.indexOf(s));
        }
        return res;
    }
}
